/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mytdev.commons.lang.bean;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;

/**
 *
 * @author Yann D'Isanto
 */
public class BeanManager {

    /**
     * Invokes methods annotated with the @OnClose annotation from the specified object.
     * @param object the object.
     * @throws com.mytdev.commons.lang.bean.BeanManager.CloseException if an error 
     * occurs when invoking OnClose annotated methods.
     */
    public static void close(Object object) throws CloseException {
        try {
            invokeAnnotatedMethods(object, OnClose.class);
        } catch (Exception ex) {
            throw new CloseException(ex.getMessage(), ex);
        }
    }

    /**
     * Invokes methods annotated with the @OnInit annotation from the specified object.
     * @param object the object.
     * @throws com.mytdev.commons.lang.bean.BeanManager.InitException if an error 
     * occurs when invoking OnInit annotated methods.
     */
    public static void init(Object object) throws InitException {
        try {
            invokeAnnotatedMethods(object, OnInit.class);
        } catch (Exception ex) {
            throw new InitException(ex.getMessage(), ex);
        }
    }

    /**
     * Invokes methods annotated with the @OnLoad annotation from the specified object.
     * @param object the object.
     * @throws com.mytdev.commons.lang.bean.BeanManager.LoadException if an error 
     * occurs when invoking OnLoad annotated methods.
     */
    public static void load(Object object) throws LoadException {
        try {
        invokeAnnotatedMethods(object, OnLoad.class);
        } catch (Exception ex) {
            throw new LoadException(ex.getMessage(), ex);
        }
    }

    /**
     * Invokes methods annotated with the @OnSave annotation from the specified object.
     * @param object the object.
     * @throws com.mytdev.commons.lang.bean.BeanManager.SaveException if an error 
     * occurs when invoking OnSave annotated methods.
     */
    public static void save(Object object) throws SaveException {
        try {
        invokeAnnotatedMethods(object, OnSave.class);
        } catch (Exception ex) {
            throw new SaveException(ex.getMessage(), ex);
        }
    }

    /**
     * Invokes methods annotated with the specified annotation from the specified object.
     * @param object the object.
     * @param annotation the annotation.
     * @throws IllegalAccessException if this Method object enforces Java language 
     * access control and the underlying method is inaccessible.
     * @throws InvocationTargetException if the underlying method throws an exception.
     */
    public static void invokeAnnotatedMethods(Object object, Class<? extends Annotation> annotation) throws IllegalAccessException, InvocationTargetException {
        LinkedList<Class<?>> classHierarchy = new LinkedList<Class<?>>();
        Class<?> objectClass = object.getClass();
        do {
            classHierarchy.add(0, objectClass);
        } while ((objectClass = objectClass.getSuperclass()) != null);

        while ((objectClass = classHierarchy.pollFirst()) != null) {
            for (Method method : objectClass.getDeclaredMethods()) {
                if (method.getAnnotation(annotation) != null) {
                    boolean accessible = method.isAccessible();
                    method.setAccessible(true);
                    method.invoke(object);
                    method.setAccessible(accessible);
                }
            }
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface OnClose {
    }

    public static class CloseException extends Exception {

        public CloseException() {
        }

        public CloseException(String message) {
            super(message);
        }

        public CloseException(String message, Throwable cause) {
            super(message, cause);
        }

        public CloseException(Throwable cause) {
            super(cause);
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface OnInit {
    }

    public static class InitException extends Exception {

        public InitException() {
        }

        public InitException(String message) {
            super(message);
        }

        public InitException(String message, Throwable cause) {
            super(message, cause);
        }

        public InitException(Throwable cause) {
            super(cause);
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface OnLoad {
    }

    public static class LoadException extends Exception {

        public LoadException() {
        }

        public LoadException(String message) {
            super(message);
        }

        public LoadException(String message, Throwable cause) {
            super(message, cause);
        }

        public LoadException(Throwable cause) {
            super(cause);
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface OnSave {
    }

    public static class SaveException extends Exception {

        public SaveException() {
        }

        public SaveException(String message) {
            super(message);
        }

        public SaveException(String message, Throwable cause) {
            super(message, cause);
        }

        public SaveException(Throwable cause) {
            super(cause);
        }
    }
}
